/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2008-2011, Salzburg Research. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * - Neither the name of the KiWi Project nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * Contributor(s):
 * sschaffe
 *
 * File Info:
 * User: sschaffe
 * Date: 2011/01/17
 * Time: 13:12
 *
 * Project: KiWi2, kiwi-core module
 */

package kiwi.core.services.sesame;

import kiwi.core.api.sesame.KiWiSesameService;
import kiwi.core.api.triplestore.KnowledgeSpaceService;
import kiwi.core.api.triplestore.LiteralService;
import kiwi.core.api.triplestore.ResourceService;
import kiwi.core.api.triplestore.TripleStore;
import kiwi.core.model.Constants;
import kiwi.core.model.content.MediaContent;
import kiwi.core.model.rdf.*;
import kiwi.core.qualifiers.transaction.Transactional;
import org.openrdf.model.*;
import org.openrdf.model.impl.URIImpl;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.RepositoryResult;
import org.slf4j.Logger;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;

/**
 * Add file description here!
 * <p/>
 * User: sschaffe
 */
@Named("kiwi.core.sesameService")
@ApplicationScoped
@Transactional
public class KiWiSesameServiceImpl implements KiWiSesameService {

    @Inject
    private Logger log;



    @Inject
    private TripleStore tripleStore;


    @Inject
    private ResourceService resourceService;
    
    
    @Inject
    private LiteralService literalService;

    @Inject
    private KnowledgeSpaceService knowledgeSpaceService;


    public KiWiSesameServiceImpl() {
    }

    /**
     * Import the triples that are contained in the repository passed as argument into the KiWi triple store.
     * Triples that do not exist yet will be created as new. Note that while the transaction is not yet committed,
     * the triples are not yet persisted in the database. This Method create for every NamedGraph in the Repository
     * a own Knowledgespace where the triple stores. If the triple has not a context it will
     * choose the defaultspace. Returns a Set containing the imported triples in
     * KiWi format.
     *
     *
     * @param connection
     * @return
     */
    @Override
    public Set<KiWiTriple> importFromSesame(RepositoryConnection connection) {
        try {
            Set<KiWiTriple> result = new HashSet<KiWiTriple>();
            List<Resource> contextResult = connection.getContextIDs().asList();

            // Create for every imported context a own knowledgespace
            for(Resource results : contextResult) {
                if(results instanceof BNode || result instanceof Literal) {
                    log.warn("invalid context type (not uri) of: " + results.stringValue());
                }

                try {
                    knowledgeSpaceService.createKnowledgeSpaceByUri(results.stringValue());
                } catch(Exception e) {
                    log.error("can not create knowledgespace: " + results.stringValue());
                }
            }

            for (RepositoryResult<Statement> r = connection.getStatements(null, null, null, false); r.hasNext();)  {
                Statement st = r.next();
                result.add(transformSesameToKiWi(st, st.getContext()));
            }
            return result;

        } catch (RepositoryException e) {
            log.error("error while exporting KiWi triples to Sesame repository",e);
            return null;
        }
    }

    /**
     * Import the triples that are contained in the repository passed as argument into the KiWi triple store.
     * Triples that do not exist yet will be created as new. Note that while the transaction is not yet committed,
     * the triples are not yet persisted in the database. If the triple has not a context it will
     * choose the defaultspace. Returns a Set containing the imported triples in
     * KiWi format. This method doesnt create a knowledgespace type for every given context.
     *
     *
     * @param connection
     * @return
     */
    @Override
    public Set<KiWiTriple> importFromSesameWithoutSpaceCreation(RepositoryConnection connection) {
        try {
            Set<KiWiTriple> result = new HashSet<KiWiTriple>();
            List<Resource> contextResult = connection.getContextIDs().asList();

            for (RepositoryResult<Statement> r = connection.getStatements(null, null, null, false); r.hasNext();)  {
                Statement st = r.next();
                result.add(transformSesameToKiWi(st, st.getContext()));
            }
            return result;

        } catch (RepositoryException e) {
            log.error("error while exporting KiWi triples to Sesame repository",e);
            return null;
        }
    }

    /**
     * Import the triples that are contained in the repository passed as argument into the KiWi triple store.
     * Triples that do not exist yet will be created as new. Note that while the transaction is not yet committed,
     * the triples are not yet persisted in the database. This method put the triple with context in the database
     * without checking for a valid context. If the triple has not a context it will choose the defaultspace. Returns a Set containing the imported triples in
     * KiWi format.
     *
     *
     * @param connection
     * @return
     */
    @Override
    public Set<KiWiTriple> importFromSesameIgnorSpaces(RepositoryConnection connection) {
        try {
            Set<KiWiTriple> result = new HashSet<KiWiTriple>();

            for (RepositoryResult<Statement> r = connection.getStatements(null, null, null, false); r.hasNext();)  {
                result.add(transformSesameToKiWi(r.next(), null));
            }
            return result;

        } catch (RepositoryException e) {
            log.error("error while exporting KiWi triples to Sesame repository",e);
            return null;
        }
    }

    /**
     * Import the triple from the Repository like importFromSesame(). Only the context is different. It will
     * choose the context of the given space for every triple.
     *
     * @param connection
     * @param space
     * @return
     */
    @Override
    public Set<KiWiTriple> importFromSesameInOwnSpace(RepositoryConnection connection, KiWiUriResource space) {
        try {
            Set<KiWiTriple> result = new HashSet<KiWiTriple>();
            List<Resource> contextResult = connection.getContextIDs().asList();

            knowledgeSpaceService.createCategorySpaceByUri(space.getUri());

            for (RepositoryResult<Statement> r = connection.getStatements(null, null, null, false); r.hasNext();)  {
                result.add(transformSesameToKiWi(r.next(), new URIImpl(space.getUri())));
            }
            return result;

        } catch (Exception e) {
            log.error("error while exporting KiWi triples to Sesame repository",e);
            return null;
        }
    }

    /**
     * Export the triples passed as first argument into the Sesame repository passed as second argument.
     *
     * @param triples
     * @param repository
     */
    @Override
    public void exportToSesame(Iterable<KiWiTriple> triples, Repository repository) {
        try {
            RepositoryConnection con = repository.getConnection();

            for(KiWiTriple triple : triples) {
                Statement stmt = KiWiSesameUtil.transformKiWiToSesame(repository,triple);
                con.add(stmt);
            }

            con.commit();
            con.close();

        } catch (RepositoryException e) {
            log.error("error while exporting KiWi triples to Sesame repository",e);
        }
    }







     /**
      * This method transforms a Sesame 2 value into a KiWi node:
      * <ul>
      * 	<li>a Sesame Literal is transformed to a KiWiLiteral by calling tripleStore.createLiteral()</li>
      *  <li>a Sesame URI is transformed to a KiWiUriResource by calling tripleStore.createUriResource()</li>
      *  <li>a Sesame BNode is transformed to a KiWiAnonResource by calling tripleStore.createAnonResourc()</li>
      * </ul>
      * @param v
      * @return
      */
     private KiWiNode transformSesameToKiWi(Value v, Resource context) {
         if(v == null) return null;

         KiWiUriResource contextSpace = null;
         if(!(context instanceof URI)) {
             contextSpace = knowledgeSpaceService.getDefaultKnowledgeSpace();
         } else {
             URI uri = (URI)context;
             contextSpace = resourceService.createUriResource(uri.getNamespace()+uri.getLocalName(),
                     knowledgeSpaceService.getSystemKnowledgeSpace());
         }

         KiWiNode result = null;
         if(v instanceof URI) {
             URI uri = (URI)v;
             try {
                result = resourceService.createUriResource(uri.getNamespace()+uri.getLocalName(), contextSpace);
             } catch(Exception e) {
                log.warn("Try to create resource with given context: " + e.getMessage());
                result = resourceService.createUriResource(uri.getNamespace()+uri.getLocalName());
             }
         } else if(v instanceof BNode) {
             BNode bnode = (BNode)v;
             try {
                result = resourceService.createAnonResource(bnode.getID(), contextSpace);
             } catch(Exception e) {
                log.warn("Try to create resource with given context: " + e.getMessage());
                result = resourceService.createUriResource(bnode.getID());
             }
         } else if(v instanceof Literal) {
             Literal literal = (Literal)v;

             Locale language;
             if(literal.getLanguage() != null) {
                 language = new Locale(literal.getLanguage());
             } else {
                 language = null;
             }


             // currently, type is set to String
             if(literal.getDatatype() == null) {
                 result = literalService.createLiteral(literal.getLabel(), language, Constants.NS_XSD+"string");
             } else if(literal.getDatatype().getLocalName().equals("string")) {
                 result = literalService.createLiteral(literal.getLabel(), language, Constants.NS_XSD+"string");
             } else if(literal.getDatatype().getLocalName().equals("integer")) {
                 result = literalService.createLiteral(literal.intValue(), language, Constants.NS_XSD+"integer");
             } else if(literal.getDatatype().getLocalName().equals("double")) {
                 result = literalService.createLiteral(literal.doubleValue(), language, Constants.NS_XSD+"double");
             } else if(literal.getDatatype().getLocalName().equals("float")) {
                 result = literalService.createLiteral(literal.floatValue(), language, Constants.NS_XSD+"float");
             } else if(literal.getDatatype().getLocalName().equals("long")) {
                 result = literalService.createLiteral(literal.longValue(), language, Constants.NS_XSD+"long");
             } else if(literal.getDatatype().getLocalName().equals("boolean")) {
                 result = literalService.createLiteral(literal.booleanValue(), language, Constants.NS_XSD+"boolean");
             } else if(Constants.NS_KIWI_CORE.equals(literal.getDatatype().getNamespace()) &&
                       "TextContent".equals(literal.getDatatype().getLocalName())) {
                 result = literalService.createTextContentLiteral(literal.stringValue(), language);
             } else if(Constants.NS_KIWI_CORE.equals(literal.getDatatype().getNamespace()) &&
                       "MediaContent".equals(literal.getDatatype().getLocalName())) {
                 MediaContent m = new MediaContent(literal.stringValue().getBytes(),null);
                 result = literalService.createMediaContentLiteral(m);
             } else {
                 result = literalService.createLiteral(
                         literal.getLabel(),
                         language,
                         literal.getDatatype().toString());

             }
         }

         return result;
      }

     /**
      * This method transforms a Sesame 2 statement into a KiWi extended triple. The transformation
      * calls tripleStore.createTriple().
      *
      * @param stmt the Sesame statement to transform
      * @return a persisted KiWiTriple
      */
     private KiWiTriple transformSesameToKiWi(Statement stmt, Resource spaceContext) {
         // in KiWi's data model, every statement is uniquely identified by its context, so we can use the
         // context URI as unique identifier in the triple pool
         KiWiTriple result = null;
         Resource context = spaceContext;

         if(stmt.getContext() != null) {
             if(context == null) {
                 context  = new URIImpl(knowledgeSpaceService.getDefaultKnowledgeSpace().getUri());
             }

             KiWiResource    subject  = (KiWiResource)          transformSesameToKiWi(stmt.getSubject(),context);
             KiWiUriResource property = (KiWiUriResource)       transformSesameToKiWi(stmt.getPredicate(),context);
             KiWiNode        object   =                         transformSesameToKiWi(stmt.getObject(),context);
             KiWiUriResource tripleContext  = (KiWiUriResource) transformSesameToKiWi(stmt.getContext(),
                     new URIImpl(knowledgeSpaceService.getSystemKnowledgeSpace().getUri()));

             result = tripleStore.createTriple(subject, property, object, tripleContext);

         } else {
             
             if(context == null) {
                context  = new URIImpl(knowledgeSpaceService.getDefaultKnowledgeSpace().getUri());
             }

             KiWiResource    subject  = (KiWiResource)          transformSesameToKiWi(stmt.getSubject(),context);
             KiWiUriResource property = (KiWiUriResource)       transformSesameToKiWi(stmt.getPredicate(),context);
             KiWiNode        object   =                         transformSesameToKiWi(stmt.getObject(),context);
             KiWiUriResource tripleContext  = (KiWiUriResource) transformSesameToKiWi(stmt.getContext(),
                     new URIImpl(knowledgeSpaceService.getSystemKnowledgeSpace().getUri()));

             result = tripleStore.createTriple(subject, property, object, tripleContext);
         }


         return result;
     }



     /**
      * This method transforms a Sesame 2 statement into a KiWi extended triple. The transformation
      * is unchecked - it returns a newly allocated triple which may yield duplicates in the
      * triple store (use with care!)
      *
      * @param stmt the Sesame statement to transform
      * @param systemSpace soaceuri for the description of a space
      * @param defaultSpace spaceuri for new triples
      * @return a persisted KiWiTriple
      */
     public static  KiWiTriple uncheckedSesameToKiWi(Statement stmt, KiWiUriResource systemSpace, KiWiUriResource defaultSpace) {
         // in KiWi's data model, every statement is uniquely identified by its context, so we can use the
         // context URI as unique identifier in the triple pool
         KiWiTriple result = null;

         KiWiUriResource uriGraphContent = systemSpace;
         uriGraphContent.setContext(uriGraphContent);

         if(stmt.getContext() != null) {
             KiWiUriResource context  = (KiWiUriResource) uncheckedSesameToKiWi(stmt.getContext(),systemSpace);
             KiWiResource    subject  = (KiWiResource)    uncheckedSesameToKiWi(stmt.getSubject(),context);
             KiWiUriResource property = (KiWiUriResource) uncheckedSesameToKiWi(stmt.getPredicate(),context);
             KiWiNode        object   =                   uncheckedSesameToKiWi(stmt.getObject(),context);

             result = new KiWiTriple(subject, property, object, context);

         } else {

             KiWiUriResource defaultContext  = (KiWiUriResource) uncheckedSesameToKiWi(new URIImpl(defaultSpace.getUri()),uriGraphContent);
             KiWiResource    subject  = (KiWiResource)    uncheckedSesameToKiWi(stmt.getSubject(),defaultContext);
             KiWiUriResource property = (KiWiUriResource) uncheckedSesameToKiWi(stmt.getPredicate(),defaultContext);
             KiWiNode        object   =                   uncheckedSesameToKiWi(stmt.getObject(),defaultContext);
             result = new KiWiTriple(subject, property, object, defaultContext);
         }


         return result;
     }


     /**
      * This method transforms a Sesame 2 value into a KiWi node:
      * The transformation is unchecked - it returns a newly allocated triple which may yield duplicates in the
      * triple store (use with care!)
      * <ul>
      * 	<li>a Sesame Literal is transformed to a KiWiLiteral </li>
      *  <li>a Sesame URI is transformed to a KiWiUriResource </li>
      *  <li>a Sesame BNode is transformed to a KiWiAnonResource </li>
      * </ul>
      * @param v
      * @return
      */
     public static  KiWiNode uncheckedSesameToKiWi(Value v, KiWiUriResource context) {
         if(v == null) return null;

         KiWiNode result = null;

         if(v instanceof URI) {
             URI uri = (URI)v;
             result = new KiWiUriResource(uri.getNamespace()+uri.getLocalName());
             ((KiWiResource) result).setContext(context);
         } else if(v instanceof BNode) {
             BNode bnode = (BNode)v;
             result = new KiWiAnonResource(bnode.getID());
             ((KiWiResource) result).setContext(context);
         } else if(v instanceof Literal) {
             Literal literal = (Literal)v;

             Locale language;
             if(literal.getLanguage() != null) {
                 language = new Locale(literal.getLanguage());
             } else {
                 language = null;
             }


             String type = null;

             // currently, type is set to String
             if(literal.getDatatype() == null) {
                 type = Constants.NS_XSD+"string";
                 result = new KiWiStringLiteral(literal.stringValue(), language, type);
              } else if(literal.getDatatype().getLocalName().equals("string")) {
                 type = Constants.NS_XSD+"string";
                 result = new KiWiStringLiteral(literal.stringValue(), language, type);
             } else if(literal.getDatatype().getLocalName().equals("integer")) {
                 type = Constants.NS_XSD+"integer";
                 result = new KiWiIntLiteral(literal.longValue(), language, type);
             } else if(literal.getDatatype().getLocalName().equals("double")) {
                 type = Constants.NS_XSD+"double";
                 result = new KiWiDoubleLiteral(literal.doubleValue(), language, type);
             } else if(literal.getDatatype().getLocalName().equals("float")) {
                 type = Constants.NS_XSD+"float";
                 result = new KiWiDoubleLiteral(literal.doubleValue(), language, type);
             } else if(literal.getDatatype().getLocalName().equals("long")) {
                 type = Constants.NS_XSD+"long";
                 result = new KiWiIntLiteral(literal.longValue(), language, type);
             } else if(literal.getDatatype().getLocalName().equals("boolean")) {
                 type = Constants.NS_XSD+"boolean";
             } else {
                 type = literal.getDatatype().stringValue();
             }
         }

         return result;
      }


}
